import { Directive, Input, NgZone, ElementRef, AfterViewInit, OnDestroy, DoCheck } from '@angular/core';
import { takeUntil, finalize } from 'rxjs/operators';
import { Platform } from '@angular/cdk/platform';
import { fromEvent, merge, Subject } from 'rxjs';
import { DomEventService } from '../core/api';

export interface AutoSizeType {
  minRows?: number;
  maxRows?: number;
}

export function isAutoSizeType(value: string | boolean | AutoSizeType): value is AutoSizeType {
  return typeof value !== 'string' && typeof value !== 'boolean' && (!!value.maxRows || !!value.minRows);
}

@Directive({
  selector: 'textarea[lv-autosize], textarea[lvAutosize]',
  exportAs: 'lvAutosize',
  host: {
    class: 'lv-autosize',
    rows: '1',
  },
})
export class AutosizeDirective implements AfterViewInit, OnDestroy, DoCheck {
  _initialHeight: any;
  private inputGap = 10; // 输入间隙
  private lineHeight: number; // 缓存行高
  private previousValue: string;
  private _minRows: number | undefined;
  private _maxRows: number | undefined;
  private destroy$ = new Subject<void>();
  private previousMinRows: number | undefined;
  private _autoSize: boolean | AutoSizeType = false;
  private el: HTMLTextAreaElement | HTMLInputElement = this.elementRef.nativeElement;

  constructor(
    private elementRef: ElementRef,
    private ngZone: NgZone,
    private platform: Platform,
    private _domEventService: DomEventService,
  ) {}

  get lvAutosize(): string | boolean | AutoSizeType {
    return this._autoSize;
  }

  @Input()
  set lvAutosize(value: string | boolean | AutoSizeType) {
    if (typeof value === 'string') {
      this._autoSize = true;
    } else if (isAutoSizeType(value)) {
      this._autoSize = value;
      this._minRows = value.minRows;
      this.setElMinHeight();
      this._maxRows = value.maxRows;
      this.setElMaxHeight();
    }
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  ngDoCheck(): void {
    if (this.lvAutosize && this.platform.isBrowser) {
      this.AdjustSize();
    }
  }

  /**
   * 将 textarea 重置为其原始大小
   */
  reset(): void {
    if (this._initialHeight === undefined) {
      return;
    }
    this.el.style.height = this._initialHeight;
  }

  setElMinHeight(): void {
    const minHeight = this._minRows && this.lineHeight ? `${this._minRows * this.lineHeight + this.inputGap}px` : null;

    if (minHeight) {
      this.el.style.minHeight = minHeight;
    }
  }

  setElMaxHeight(): void {
    const maxHeight = this._maxRows && this.lineHeight ? `${this._maxRows * this.lineHeight + this.inputGap}px` : null;

    if (maxHeight) {
      this.el.style.maxHeight = maxHeight;
    }
  }

  ngAfterViewInit(): void {
    if (this.lvAutosize && this.platform.isBrowser) {
      this._initialHeight = this.el.style.height;
      this.AdjustSize();
      merge(this._domEventService.registerResizeListener(), fromEvent(this.elementRef.nativeElement, 'input'))
        .pipe(
          takeUntil(this.destroy$),
          finalize((): void => this._domEventService.unregisterResizeListener()),
        )
        .subscribe((): void => this.AdjustSize(true));
    }
  }

  /**
   * 调整大小以适应内容
   */
  AdjustSize(force: boolean = false): void {
    this.cacheTextareaLineHeight();

    if (!this.lineHeight) {
      return;
    }

    const textarea = this.el as HTMLTextAreaElement;
    const { value } = textarea;

    if (!force && this._minRows === this.previousMinRows && value === this.previousValue) {
      return;
    }

    const { placeholder } = textarea;
    // 避免对cdk的text-field-prebuilt.css的引入，造成对应的依赖
    textarea.classList.add('lv-textarea-autosize-measuring');
    textarea.placeholder = '';

    const height =
      Math.round((textarea.scrollHeight - this.inputGap) / this.lineHeight) * this.lineHeight + this.inputGap;
    textarea.style.height = `${height}px`;
    textarea.classList.remove('lv-textarea-autosize-measuring');
    textarea.placeholder = placeholder;

    if (typeof requestAnimationFrame !== 'undefined') {
      this.ngZone.runOutsideAngular((): number =>
        requestAnimationFrame((): void => {
          const { selectionStart, selectionEnd } = textarea;

          if (!this.destroy$.isStopped && document.activeElement === textarea) {
            textarea.setSelectionRange(selectionStart, selectionEnd);
          }
        }),
      );
    }

    this.previousValue = value;
    this.previousMinRows = this._minRows;
  }

  private cacheTextareaLineHeight(): void {
    if (this.lineHeight >= 0 || !this.el.parentNode) {
      return;
    }

    // 使用克隆元素，因为我们必须覆盖一些样式
    const cloneNode = this.el.cloneNode(false) as HTMLTextAreaElement;
    cloneNode.rows = 1;

    // 使用 `position: absolute` 这样就不会导致浏览器布局和使用`visibility: hidden` 以便不渲染任何内容。清除任何其他样式会影响高度。
    cloneNode.style.position = 'absolute';
    cloneNode.style.visibility = 'hidden';
    cloneNode.style.border = 'none';
    cloneNode.style.padding = '0';
    cloneNode.style.height = '';
    cloneNode.style.minHeight = '';
    cloneNode.style.maxHeight = '';

    // bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=33654
    cloneNode.style.overflow = 'hidden';

    this.el.parentNode.appendChild(cloneNode);
    this.lineHeight = cloneNode.clientHeight - this.inputGap - 1;
    this.el.parentNode.removeChild(cloneNode);

    this.setElMinHeight();
    this.setElMaxHeight();
  }
}
